package soar.test.image;

import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;

public class ImageUtil
{
    private static final String __CLASS__ = ImageUtil.class.getName();

    private final static int ALIGN_LEFT = 0;
    private final static int ALIGN_MIDDLE = 1;

    public static byte[] merge(WeatherData weather_data,
            String bg_img_file, String ad_img_file) throws Exception
    {
        final String __METHOD__ = "merge";

        BufferedImage bg_img = get_img(bg_img_file);
        if (bg_img == null)
        {
            throw new Exception(String.format(
                    "%s.%s: bg_img_file:%s not found", __CLASS__,
                    __METHOD__, bg_img_file));
        }

        BufferedImage ad_img = get_img(ad_img_file);
        if (ad_img == null)
        {
            throw new Exception(String.format(
                    "%s.%s: ad_img_file:%s not found", __CLASS__,
                    __METHOD__, ad_img_file));
        }

        int x_left = 100;
        int x_middle = bg_img.getWidth() / 2;
        int x_right = 473;

        // 字体
        InputStream font_is = ImageUtil.class
                .getResourceAsStream("/misc/wqy-microhei.ttc");
        // InputStream font_is = ImageUtil.class
        // .getResourceAsStream("/misc/方正硬笔行书简体ok.ttf");
        Font font = Font.createFont(Font.TRUETYPE_FONT, font_is);

        // 合并
        BufferedImage img = new BufferedImage(bg_img.getWidth()
                + ad_img.getWidth(), bg_img.getHeight(),
                BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.drawImage(bg_img, null, 0, 0);
        g2d.drawImage(ad_img, null, bg_img.getWidth(), 0);

        WeatherData.Day today = weather_data.days[0];

        // 当天天气图标
        draw_icon(g2d, today.desc_code, 0, ALIGN_MIDDLE, x_middle, 160);

        // 当天天气文字
        draw_string(g2d, font, 35, WeatherDesc.getText(today.desc_code),
                ALIGN_MIDDLE, x_middle, 380);

        // 当天气温
        draw_string(g2d, font, 83, today.air_temperature, ALIGN_LEFT,
                x_left, 508);

        // 当天日期
        draw_string(g2d, font, 20, today.date, ALIGN_LEFT, x_right, 467);

        // 当天星期
        draw_string(g2d, font, 20, today.day, ALIGN_LEFT, x_right, 503);

        // 未来3天
        int y_step = 143;
        int y_at_begin = 640;
        int y_text_begin = 677;
        int y_icon_begin = 607;
        int y_date_begin = 637;
        int y_day_begin = 673;
        for (int i = 1; i < weather_data.days.length; ++i)
        {
            WeatherData.Day future = weather_data.days[i];

            // 气温
            draw_string(g2d, font, 30, future.air_temperature, ALIGN_LEFT,
                    x_left, y_at_begin + (i - 1) * y_step);
            // 天气文字
            draw_string(g2d, font, 23,
                    WeatherDesc.getText(future.desc_code), ALIGN_LEFT,
                    x_left, y_text_begin + (i - 1) * y_step);
            // 天气图标
            draw_icon(g2d, future.desc_code, 40, ALIGN_MIDDLE, x_middle,
                    y_icon_begin + (i - 1) * y_step);
            // 日期
            draw_string(g2d, font, 20, future.date, ALIGN_LEFT, x_right,
                    y_date_begin + (i - 1) * y_step);
            // 星期
            draw_string(g2d, font, 20, future.day, ALIGN_LEFT, x_right,
                    y_day_begin + (i - 1) * y_step);
        }

        g2d.dispose();

        return output_jpeg(img);
    }

    private static BufferedImage get_img(String img_file) throws Exception
    {
        InputStream is = ImageUtil.class.getResourceAsStream("/img/"
                + img_file);
        BufferedImage img = ImageIO.read(is);
        is.close();
        return img;
    }

    private static void copy_background_mp3(String output_dir)
            throws Exception
    {
        final String __METHOD__ = "copy_background_mp3";

        String mp3 = "background.mp3";
        String mp3_file = output_dir + "/background.mp3";

        File file = new File(mp3_file);
        if (file.exists())
        {
            if (!file.isFile() || !file.canRead())
            {
                throw new Exception(String.format("%s.%s: %s invalid",
                        __CLASS__, __METHOD__, mp3_file));
            }
            return;
        }

        try
        {
            InputStream file_in = ImageUtil.class
                    .getResourceAsStream("/misc/" + mp3);
            FileOutputStream file_out = new FileOutputStream(new File(
                    mp3_file));
            byte[] buf = new byte[1024];
            int len;
            while ((len = file_in.read(buf)) > 0)
            {
                file_out.write(buf, 0, len);
            }
            file_out.close();
            file_in.close();

            System.err.println(String.format(
                    "%s.%s: copy from resources to %s", __CLASS__,
                    __METHOD__, mp3_file));
        }
        catch (Exception e)
        {
            throw new Exception(String.format("%s.%s: fail, %s",
                    __CLASS__, __METHOD__, e));
        }
    }

    private static void draw_icon(Graphics2D g2d, String code,
            final int shrink_ratio, final int align, int x, int y)
            throws Exception
    {
        final String __METHOD__ = "draw_icon";

        InputStream is = ImageUtil.class
                .getResourceAsStream(String.format("/icon/%s_%s.png",
                        code, WeatherDesc.getText(code)));
        if (is != null)
        {
            BufferedImage icon = ImageIO.read(is);
            is.close();

            if (shrink_ratio > 0)
            {
                double ratio = (double) shrink_ratio / 100;
                int width = (int) (icon.getWidth() * ratio);
                int height = (int) (icon.getHeight() * ratio);
                BufferedImage new_icon = new BufferedImage(width, height,
                        BufferedImage.TYPE_3BYTE_BGR);
                Graphics2D g = new_icon.createGraphics();
                new_icon = g.getDeviceConfiguration()
                        .createCompatibleImage(width, height,
                                Transparency.TRANSLUCENT);
                g.dispose();
                g = new_icon.createGraphics();
                g.drawImage(icon.getScaledInstance(width, height,
                        Image.SCALE_SMOOTH), 0, 0, null);
                g.dispose();

                icon = new_icon;
            }

            if (align == ALIGN_MIDDLE)
            {
                x -= icon.getWidth() / 2;
            }

            g2d.drawImage(icon, x, y, null);
        }
        else
        {
            System.err.println(String.format(
                    "%s.%s: resource:%s not found", __CLASS__, __METHOD__,
                    code));
        }
    }

    private static void draw_string(Graphics2D g2d, Font font,
            int font_size, String text, final int align, int x, int y)
    {
        Font font_derive = font.deriveFont(Font.PLAIN, font_size);
        g2d.setFont(font_derive);
        if (align == ALIGN_MIDDLE)
        {
            x -= text.length() * font_size / 2;
        }
        g2d.drawString(text, x, y);
    }

    private static byte[] output_jpeg(BufferedImage img) throws Exception
    {
        // ByteArrayOutputStream out = new ByteArrayOutputStream();
        // ImageIO.write(img, "jpeg", out);
        // return out.toByteArray();

        Iterator<ImageWriter> iter = ImageIO
                .getImageWritersByFormatName("jpeg");
        ImageWriter writer = (ImageWriter) iter.next();

        ImageWriteParam iwp = writer.getDefaultWriteParam();
        iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        iwp.setCompressionQuality(0.9f);
        iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
        ColorModel cm = ColorModel.getRGBdefault();
        iwp.setDestinationType(new ImageTypeSpecifier(cm, cm
                .createCompatibleSampleModel(16, 16)));

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        IIOImage image = new IIOImage(img, null, null);
        writer.setOutput(ImageIO.createImageOutputStream(output));
        writer.write(null, image, iwp);

        return output.toByteArray();
    }

    public static byte[] convertToVideo(byte[] jpeg, String video_format,
            String output_dir) throws Exception
    {
        final String __METHOD__ = "convertToVideo";

        String output_name = "" + Thread.currentThread().getId();
        String jpeg_file = String.format("%s_%s.jpg", output_name,
                video_format);
        String bash_file = String.format("%s_%s.sh", output_name,
                video_format);

        // 生成命令
        StringBuilder jpeg_files = new StringBuilder();
        jpeg_files.append(jpeg_file);
        for (int i = 1; i < 200; ++i)
        {
            jpeg_files.append(",");
            jpeg_files.append(jpeg_file);
        }

        String video_file;
        ArrayList<String> commands = new ArrayList<String>();
        switch (video_format.toUpperCase())
        {
        case "MPEG2":
            video_file = String.format("%s_%s.mpg", output_name,
                    video_format);
            commands.add(String
                    .format("mencoder 'mf://%s'"
                            + " -mf fps=25:type=jpg"
                            + " -oac lavc -audiofile background.mp3"
                            + " -srate 48000 -af lavcresample=48000"
                            + " -ovc lavc"
                            + " -lavcopts vcodec=mpeg2video:vpass=1"
                            + ":vrc_buf_size=1835:vrc_maxrate=2048:vbitrate=2048"
                            + ":keyint=200:vstrict=0:aspect=4/3"
                            + ":acodec=mp2:abitrate=384"
                            + " -of mpeg -mpegopts format=dvd:tsaf"
                            + " -ofps 25"
                            + " -vf-add scale=660:536,harddup -vf-add expand=720:576:20:20:0"
                            + " -o %s", jpeg_files.toString(), video_file));
            // commands.add(String
            // .format("mencoder 'mf://%s'"
            // + " -mf fps=25:type=jpg"
            // + " -oac lavc -audiofile background.mp3"
            // + " -srate 48000 -af lavcresample=48000"
            // + " -ovc lavc"
            // + " -lavcopts vcodec=mpeg2video:vpass=2"
            // + ":vrc_buf_size=1835:vrc_maxrate=2048:vbitrate=2048"
            // + ":keyint=50:vstrict=0:aspect=4/3"
            // + ":acodec=mp2:abitrate=384"
            // + " -of mpeg -mpegopts format=dvd:tsaf"
            // + " -ofps 25"
            // + " -vf-add scale=660:536,harddup -vf-add expand=720:576:20:20:0"
            // + " -o %s", jpeg_files.toString(), video_file));
            break;
        case "MPEG4":
            video_file = String.format("%s_%s.mp4", output_name,
                    video_format);
            commands.add(String.format("mencoder 'mf://%s'"
                    + " -mf fps=25:type=jpg"
                    + " -oac copy -audiofile background.mp3"
                    + " -ovc lavc -lavcopts vcodec=mpeg4:mbd=2"
                    + ":trell:vbitrate=1200:keyint=200:aspect=4/3"
                    + " -ofps 25"
                    + " -vf scale=660:536,harddup,expand=720:576:20:20:0"
                    + " -o %s", jpeg_files.toString(), video_file));
            break;
        case "H264":
            video_file = String.format("%s_%s.mp4", output_name,
                    video_format);
            commands.add(String.format("mencoder 'mf://%s'"
                    + " -mf fps=25:type=jpg"
                    + " -oac copy -audiofile background.mp3" + " -sws 9"
                    + " -ovc x264 -x264encopts pass=1:bitrate=1200"
                    + ":turbo=2:keyint=200" + " -ofps 25"
                    + " -vf scale=660:536,harddup,expand=720:576:20:20:0"
                    + " -o %s", jpeg_files.toString(), video_file));
            break;
        default:
            return null;
        }

        BufferedWriter bw = new BufferedWriter(new FileWriter(
                String.format("%s/%s", output_dir, bash_file)));
        bw.write("#!/usr/bin/env bash\n");
        bw.write("\n");
        for (String command : commands)
        {
            bw.write(command);
            bw.write("\n");
        }
        bw.write("\n");
        bw.close();

        // 准备背景音乐
        copy_background_mp3(output_dir);

        // 写图像文件
        FileOutputStream jpeg_out = new FileOutputStream(new File(
                String.format("%s/%s", output_dir, jpeg_file)));
        jpeg_out.write(jpeg);
        jpeg_out.close();

        // 执行命令
        Runtime runtime = Runtime.getRuntime();
        Process process = null;
        int retval = 1;

        try
        {
            String command = String.format("bash %s", bash_file);
            process = runtime.exec(command, null, new File(output_dir));

            final BufferedReader in_br = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));
            final BufferedReader err_br = new BufferedReader(
                    new InputStreamReader(process.getErrorStream()));

            Thread in_thread = new Thread()
            {
                public void run()
                {
                    try
                    {
                        String line;
                        while ((line = in_br.readLine()) != null)
                        {
                            System.out.println(String.format("%s", line));
                        }
                    }
                    catch (Exception e)
                    {
                        System.err.println(String.format("%s.%s",
                                __CLASS__, __METHOD__, e));
                    }
                }
            };
            in_thread.start();

            Thread err_thread = new Thread()
            {
                public void run()
                {
                    try
                    {
                        String line;
                        while ((line = err_br.readLine()) != null)
                        {
                            System.out.println(String.format("%s", line));
                        }
                    }
                    catch (Exception e)
                    {
                        System.err.println(String.format("%s.%s",
                                __CLASS__, __METHOD__, e));
                    }
                }
            };
            err_thread.start();

            in_thread.join();
            err_thread.join();

            retval = process.waitFor();
        }
        catch (Exception e)
        {
            System.err.println(String.format("%s.%s: exec fail, %s",
                    __CLASS__, __METHOD__, e));
        }
        finally
        {
            if (process != null)
            {
                process.getInputStream().close();
                process.getErrorStream().close();
            }
        }

        if (retval != 0)
        {
            System.err.println(String.format("%s.%s: exec retval:%d",
                    __CLASS__, __METHOD__, retval));
            throw new Exception(String.format("%s: retval:%d", __METHOD__,
                    retval));
        }

        // 读生成的视频
        FileInputStream video_in = new FileInputStream(String.format(
                "%s/%s", output_dir, video_file));
        byte[] video_b = new byte[video_in.available()];
        video_in.read(video_b);
        video_in.close();

        return video_b;
    }
}
